package com.xianyun.rpc.serverProxy;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xianyun.rpc.RpcApplication;
import com.xianyun.rpc.config.RpcConfig;
import com.xianyun.rpc.constant.RpcConstant;
import com.xianyun.rpc.fault.retry.RetryStrategy;
import com.xianyun.rpc.fault.retry.RetryStrategyFactory;
import com.xianyun.rpc.fault.tolerant.TolerantStrategy;
import com.xianyun.rpc.fault.tolerant.TolerantStrategyFactory;
import com.xianyun.rpc.loadBalance.LoadBalancer;
import com.xianyun.rpc.loadBalance.LoadBalancerFactory;
import com.xianyun.rpc.model.RpcRequest;
import com.xianyun.rpc.model.RpcResponse;
import com.xianyun.rpc.model.ServiceMetaInfo;
import com.xianyun.rpc.registry.Registry;
import com.xianyun.rpc.registry.RegistryFactory;
import com.xianyun.rpc.serializer.Serializer;
import com.xianyun.rpc.serializer.SerializerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JDK 动态代理生成器
 * 为服务消费者提供服务
 *
 * @author xianYun
 * @version 1.0
 **/
public class ServiceProxy implements InvocationHandler {
    //可以简单理解代理对象调用方法底层其实是在调用 invoke 方法。
    //现在需要将本地调用方法的信息传递到服务提供者
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1. 指定序列化器
        //JdkSerializer serializer = new JdkSerializer();
        Serializer serializer = SerializerFactory.getSerializer(RpcApplication.getRpcConfig().getSerializer());
        //2. 构建请求
        String serviceName = method.getDeclaringClass().getName();
        RpcRequest rpcRequest = RpcRequest.builder().serviceName(serviceName)
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .build();
        //3. 序列化请求，并发送
        byte[] bodyBytes = serializer.serialize(rpcRequest);
        //硬编码的地址
        //HttpResponse result = HttpRequest.post("http://localhost:8080")
        //        .body(bodyBytes )
        //        .execute();

        //进阶版：从注册中心获取服务提供者的地址
        RpcConfig rpcConfig = RpcApplication.getRpcConfig();
        Registry registry = RegistryFactory.getInstance(rpcConfig.getRegistryConfig().getRegistry());
        ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
        serviceMetaInfo.setServiceName(serviceName);
        serviceMetaInfo.setServiceVersion(RpcConstant.DEFAULT_SERVICE_VERSION);
        List<ServiceMetaInfo> serviceMetaInfoList = registry.serviceDiscovery(serviceMetaInfo.getServiceKey());
        if (CollUtil.isEmpty(serviceMetaInfoList)) {
            throw new RuntimeException("暂无服务地址");
        }
        //region 暂时先取第一个 现改用负载均衡
        //ServiceMetaInfo selectedServiceMetaInfo = serviceMetaInfoList.get(0);
        //endregion
        //负载均衡
        LoadBalancer loadBalancer = LoadBalancerFactory.getInstance(rpcConfig.getLoadBalancer());
        Map<String, Object> requestParams = new HashMap<>();
        requestParams.put("methodName", rpcRequest.getMethodName());
        ServiceMetaInfo selectedServiceMetaInfo = loadBalancer.select(requestParams, serviceMetaInfoList);

        System.out.println("服务提供者地址：" + selectedServiceMetaInfo.getServiceAddress());
        //4. 接收响应，并反序列化   引入重试器
        RetryStrategy retryStrategy = RetryStrategyFactory.
                getInstance(RpcApplication.getRpcConfig().getRetryStrategy());

        RpcResponse rpcResponse = retryStrategy.doRetry(() -> {// 发送请求
            try (HttpResponse httpResponse = HttpRequest.post(selectedServiceMetaInfo.getServiceAddress())
                    .body(bodyBytes)
                    .execute()) {
                byte[] result = httpResponse.bodyBytes();
                // 反序列化
                RpcResponse response = serializer.deserialize(result, RpcResponse.class);
                return response;
            } catch (Exception e) {
                // 容错机制
                TolerantStrategy tolerantStrategy = TolerantStrategyFactory.getInstance(rpcConfig.getTolerantStrategy());
                return tolerantStrategy.doTolerant(null, e);
            }
        });

        return rpcResponse.getData();
    }

}
